﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Net.Sockets;
using System.Reflection;
using IRC4N00bz.ArgTypes;

namespace IRC4N00bz {
    public class Bot {

        #region "A bunch of static (implied, not enforced) constants"
        public static string SERVER = "irc.server.com";
        public static int PORT = 6667;
        public static string USER = "USER " + Environment.UserName + " 8 * :I'm an IRC C# (CSharp) bot, written to learn sockets and data flow.";
        public static string NICK = "ChSrv";
        public static string SWITCH = "!";
        public static string LOGDIR = Environment.CurrentDirectory + @"\Logs";
        public static string CHANNEL = "#dev";
        public StreamWriter writer;
        public static int SENDDELAY = 400;
        public static List<Plugin> plugins;
        #endregion "A bunch of static (implied, not enforced) constants"

        public bool Connected = false;

        string inputLine;
        StreamReader reader;
        NetworkStream stream;
        TcpClient irc;

        #region "Class Constructors"
        public Bot(string server, int port, string username, string channel) {
            plugins = new List<Plugin>();
            SERVER = server;
            PORT = port;
            NICK = username;
            CHANNEL = channel;

            if (!Directory.Exists(Bot.LOGDIR)) {
                Directory.CreateDirectory(Bot.LOGDIR);
            }

            #region " Load Plugins "
            try {
                object[] o = { this };
                string loadingPluginsMessage = "=================== Loading Plugins ===================";
                Console.WriteLine(loadingPluginsMessage);
                foreach (System.IO.FileInfo f in new System.IO.DirectoryInfo("Plugins").GetFiles())
                    if (f.Extension == ".dll") {
                        Console.WriteLine("Loading Plugins from Assembly '" + f.Name + "' ...");
                        Assembly a = System.Reflection.Assembly.LoadFile(f.FullName);
                        foreach (Type t in a.GetTypes())
                            if (t.BaseType == typeof(Plugin)) {
                                Console.WriteLine("\t\t- " + t.Name);
                                Plugin p = (Plugin)Activator.CreateInstance(t, o);
                                plugins.Add(p);
                            }
                    }
                Console.Write(Environment.NewLine);
                for (int i = 0; i < loadingPluginsMessage.Length - 1; i++) {
                    Console.Write("=");
                }
                Console.WriteLine("={0}{0}", Environment.NewLine);

            } catch (Exception err) {
                Console.WriteLine("LoadPlugin->Error: {0}", err.Message);
            }
            #endregion

        }
        #endregion "Class Constructors"

        #region "Class Methods"

        public void Connect() {
            try {
                //connect to the network stream
                irc = new TcpClient(SERVER, PORT);
                stream = irc.GetStream();

                //connect the stream reader/writer to the networkstreams.
                reader = new StreamReader(stream);
                writer = new StreamWriter(stream);

                //start ping sender to prevent 'null use' disconnects
                PingSender p = new PingSender(this);
                p.Start();

                //send logon info and join channel;
                RFC_SendRaw(USER);
                RFC_SendRaw("NICK " + NICK);

                //fire onConnected event
                Connected = true;
                if (OnConnected != null) {
                    rawMsgArgs.RawMessage = "You're connected!";
                    OnConnected(rawMsgArgs);
                }

                RFC_SendRaw("JOIN " + CHANNEL);

                //listen to the socket/streams
                Listen();
            } catch (Exception err) {
                //connection failed... what happend?
                Console.WriteLine("ConnectionError: {0}", err.Message);
                Thread.Sleep(5000);
                Connect();
            }
        }

        #region "Twitter helpers"

        public void SendToTwitter(string Username, string Password, string message) {
            //if (message.Length < 140) {
            //    if (Twitterizer.Framework.Twitter.VerifyCredentials(Username, Password)) {
            //        Twitterizer.Framework.Twitter t = new Twitterizer.Framework.Twitter(Username, Password);
            //        t.Status.Update(message);
            //    }
            //}
        }
        #endregion "Twitter helpers"

        public void RFC_SendRaw(string rawMessage) {
            writer.WriteLine(rawMessage);
            writer.Flush();
            Thread.Sleep(SENDDELAY);
        }

        private void Listen() {
            while (true) {
                try {
                    while ((inputLine = reader.ReadLine()) != null) {
                        KickChannelMessage();
                        KickPrivateMessage();
                        //Todo: Add more event kickers!


                        //finaly, kick everything as a raw message
                        KickRawEvent();
                    }

                    //null line.. we probably got disconnected...
                    writer.Close();
                    reader.Close();
                    irc.Close();
                } catch (Exception err) {
                }
            }
        }

        private void KickPrivateMessage() {
            string[] parts = inputLine.Split(' ');
            if (parts[1] == "PRIVMSG") {
                
                if (parts[2].ToLower() == Bot.NICK.ToLower()) {
                    //kick the shiz!
                    if (OnPrivateMessage != null) {
                        string who = inputLine.Substring(1);
                        who = who.Substring(0, inputLine.IndexOf("!") - 1);
                        privateMsgArgs.From = who;
                        privateMsgArgs.RawMessage = inputLine;

                        //buggy!
                        //privateMsgArgs.Message = inputLine.Substring(inputLine.IndexOf(parts[3]) + 1);

                        string marker = string.Format("{0} :", Bot.NICK);
                        string message = inputLine.Substring(inputLine.IndexOf(Bot.NICK + " :") + marker.Length);
                        privateMsgArgs.Message = message;
                        OnPrivateMessage(privateMsgArgs);
                    }
                }
            }
        }

        private void KickChannelMessage() {
            string[] parts = inputLine.Split(' ');
            if (parts[1] == "PRIVMSG") {
                if (parts[2].StartsWith(@"#")) {      
                    //kick the shiz!
                    //buggy!
                    //channelMsgArg.Message = inputLine.Substring(inputLine.IndexOf(parts[3]) + 1);
                    if (OnChannelMessage != null) {
                        channelMsgArg.Channel = parts[2];
                        channelMsgArg.RawMessage = inputLine;

                        string who = inputLine.Substring(1);
                        who = who.Substring(0, inputLine.IndexOf("!") - 1);
                        channelMsgArg.Who = who;

                        string marker = string.Format("{0} :", channelMsgArg.Channel);
                        string message = inputLine.Substring(inputLine.IndexOf(channelMsgArg.Channel + " :") + marker.Length);
                        channelMsgArg.Message = message;    
                        OnChannelMessage(channelMsgArg);
                    }
                }
            }
        }

        private void KickRawEvent() {
            if (OnRawMessage != null) {
                rawMsgArgs.RawMessage = inputLine;
                OnRawMessage(rawMsgArgs);
            }
        }

        public void SendMessage(string to, string message) {            
            RFC_SendRaw(string.Format("PRIVMSG {0} :{1}", to, message));
        }

        public void SendMessage(string to, string message, MessageType.Type type) {            
            switch (type) {
                case MessageType.Type.Notice:
                    RFC_SendRaw(string.Format("NOTICE {0} :{1}", to, message));
                    break;
                case MessageType.Type.Message:
                    RFC_SendRaw(string.Format("PRIVMSG {0} :{1}", to, message));
                    break;
                default:
                    RFC_SendRaw(string.Format("PRIVMSG {0} :{1}", to, message));
                    break;
            }
        }

        public void SendNotice(string to, string message) {
            RFC_SendRaw(string.Format("NOTICE {0} :{1}", to, message));
        }

        #endregion "Class Methods"

        #region "Events & ArgHolders"
        public event IRC4N00bz.EventHandlers.ChannelMsgHandler OnChannelMessage;
        public event IRC4N00bz.EventHandlers.DeOpHandler OnDeOp;
        public event IRC4N00bz.EventHandlers.DeVoiceHandler OnDeVoice;
        public event IRC4N00bz.EventHandlers.JoinHandler OnJoin;
        public event IRC4N00bz.EventHandlers.KickHandler OnKick;
        public event IRC4N00bz.EventHandlers.OpHandler OnOp;
        public event IRC4N00bz.EventHandlers.PartHandler OnPart;
        public event IRC4N00bz.EventHandlers.PrivateMsgHandler OnPrivateMessage;
        public event IRC4N00bz.EventHandlers.RawMsgHandler OnRawMessage;
        public event IRC4N00bz.EventHandlers.RawMsgHandler OnConnected;
        public event IRC4N00bz.EventHandlers.VoiceHandler OnVoice;


        //Arg holders for our events
        private ChannelMsgArg channelMsgArg = new ChannelMsgArg();
        private DeOpArgs deOpArgs = new DeOpArgs();
        private DeVoiceArgs deVoiceArgs = new DeVoiceArgs();
        private JoinArgs joinArgs = new JoinArgs();
        private KickArgs kickArgs = new KickArgs();
        private OpArgs opArgs = new OpArgs();
        private PartArgs partArgs = new PartArgs();
        private PrivateMsgArgs privateMsgArgs = new PrivateMsgArgs();
        private RawMsgArgs rawMsgArgs = new RawMsgArgs();
        private VoiceArgs voiceArgs = new VoiceArgs();

        #endregion "Events & ArgHolders"
    }
}
